/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package axil.engine;

import axil.api.Argument;
import axil.api.AxilObject;
import axil.api.Context;
import axil.etc.Cloned;
import axil.etc.Immutable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static axil.framework.Functions.empty;


/**
 * The actual object representing an Axil API context.
 */
public class AxilContext implements Context, Cloned<AxilContext>, Immutable {
	public static final AxilContext empty = new AxilContext();

	private Context parent;
	private HashMap<String, Arg> map;


	public AxilContext(Context parent, Argument... args) {
		this.parent = parent;
		this.map = new HashMap<String, Arg>();
		for (Argument arg : args) {
			map.put(arg.key(), (Arg)arg);
		}
	}


	public AxilContext() {
		this.parent = null;
		this.map = new HashMap<String, Arg>();
	}


	private AxilContext(AxilContext other) {
		this.parent = other.parent;
		this.map = (HashMap<String, Arg>)other.map.clone();
	}


	/**
	 * Clone this object, returning a new object. The clone is sufficiently
	 * deep to ensure that any modifications to the clone, or to any member of
	 * the clone, do not impact this object.
	 */
	public AxilContext cloned() {
		return new AxilContext(this);
	}


	/**
	 * Extract the current value for the given identifier from this context.
	 *
	 * @param name
	 *	The name of the identifier, either from a simple identifier or the root
	 * 	element from a compound identifier.
	 *
	 * @return
	 * 	Returns the Axil object matching the given key. If this context does
	 * 	not contain the value, then it must delegate to the parent context. If
	 * 	the object is simply not in the context, then null is returned.
	 */
	public AxilObject at(String name) {
		Arg a = map.get(name);
		if (a == null) {
			return parent == null ? null : parent.at(name);
		}
		return a.actual();
	}


	/**
	 * Extract the current value for the given identifier from this context.
	 * This method is used only to extract root objects, not complex identifiers.
	 * For example, in the complex identifier `Shopping Cart.Items`, the get()
	 * method is used only to extract 'shopping-cart', not the entire value.
	 * This method is invoked by Accessor objects only.
	 *
	 * @param name
	 * 	The name of the identifier, either from a simple identifier or the root
	 * 	element from a compound identifier.
	 *
	 * @return
	 * 	Returns an appropriate object value. The null value may be safely
	 * 	returned. If this context does not contain the value, then it must
	 * 	delegate to the parent context. If the object is simply not in the
	 * 	context, then null is returned.
	 */
	public Object get(String name) {
		Arg a = map.get(name);
		if (a == null) {
			return parent == null ? null : parent.get(name);
		}
		return a.value();
	}


	/**
	 * Bind the given value to the name, creating a new context in the process.
	 * This makes a clone of the existing content's values and then puts the new
	 * binding in it, returning the new context. This object is not modified in
	 * any way.
	 */
	public AxilContext bind(String name, AxilObject value) {
		assert ! empty(name);
		assert value != null;

		return new AxilContext(this, new Arg(name, value));
	}


	/**
	 * Unbind the name/value pair from this context, returning a new context
	 * that does not contain the value. This object is not modified in any way.
	 */
	public AxilContext unbind(String name) {
		assert ! empty(name);

		if (! this.map.containsKey(name)) {
			return this;
		}
		AxilContext ac = cloned();
		ac.map.remove(name);
		return ac;
	}


	/**
	 * Get a list of all of the bindings contained in this context. The list
	 * returned guarantees to be immune to changing this context.
	 */
	public List<Arg> bindings() {
		return bindings(new ArrayList<Arg>());
	}


	private List<Arg> bindings(List<Arg> args) {
		for (Arg a : map.values()) {
			args.add(a);
		}
		if (parent != null) {
			((AxilContext)parent).bindings(args);
		}
		return args;
	}


	/**
	 * Return the parent context on top of which this one relies. The contexts
	 * form a chain of immutable objects, allowing for the same contexts to be
	 * used safely across threads and also allow common identifiers to be defined
	 * only once.
	 *
	 * @return
	 * 	Returns the parent context. For a custom context implementation by the
	 * 	host application, the object returned can never be null.
	 */
	public Context parent() {
		return parent;
	}
}
